/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using Apache.NMS.Util;
using NUnit.Framework;

namespace Apache.NMS.Test
{
	/// <summary>
	/// useful base class for test cases
	/// </summary>
	public abstract class NMSTestSupport
	{
		private NMSConnectionFactory NMSFactory;
		protected TimeSpan receiveTimeout = TimeSpan.FromMilliseconds(15000);
		protected string clientId;
		protected string passWord;
		protected string userName;
		protected int testRun;
		protected int idCounter;

		static NMSTestSupport()
		{
			Apache.NMS.Tracer.Trace = new NmsTracer();
		}

		public NMSTestSupport()
		{
		}

		[SetUp]
		public virtual void SetUp()
		{
			this.testRun++;
		}

		[TearDown]
		public virtual void TearDown()
		{
		}

		// Properties

		/// <summary>
		/// The connection factory interface property.
		/// </summary>
		public IConnectionFactory Factory
		{
			get
			{
				if(null == NMSFactory)
				{
					Assert.IsTrue(CreateNMSFactory(), "Error creating factory.");
				}

				return NMSFactory.ConnectionFactory;
			}
		}

		/// <summary>
		/// Name of the connection configuration filename.
		/// </summary>
		/// <returns></returns>
		protected virtual string GetConnectionConfigFileName() { return "nmsprovider-test.config"; }

		/// <summary>
		/// The name of the connection configuration that CreateNMSFactory() will load.
		/// </summary>
		/// <returns></returns>
		protected virtual string GetNameTestURI() { return "defaultURI"; }

		/// <summary>
		/// Create the NMS Factory that can create NMS Connections.
		/// </summary>
		/// <returns></returns>
		protected bool CreateNMSFactory()
		{
			return CreateNMSFactory(GetNameTestURI());
		}

		/// <summary>
		/// Return the configured URI String.  This function loads the connection
		/// settings from the configuration file.
		/// </summary>
		/// <returns></returns>
		protected string GetConfiguredConnectionURI()
		{
			Uri brokerUri = null;
			string[] paths = GetConfigSearchPaths();
			string connectionConfigFileName = GetConnectionConfigFileName();
			bool configFound = false;

			foreach(string path in paths)
			{
				string fullpath = Path.Combine(path, connectionConfigFileName);
				Tracer.Debug("\tScanning folder: " + path);

				if(File.Exists(fullpath))
				{
					Tracer.Debug("\tAssembly found!");
					connectionConfigFileName = fullpath;
					configFound = true;
					break;
				}
			}

			Assert.IsTrue(configFound, "Connection configuration file does not exist.");
			XmlDocument configDoc = new XmlDocument();

			configDoc.Load(connectionConfigFileName);
			XmlElement uriNode = (XmlElement) configDoc.SelectSingleNode(String.Format("/configuration/{0}", GetNameTestURI()));

			if(null != uriNode)
			{
				// Replace any environment variables embedded inside the string.
				brokerUri = new Uri(ReplaceEnvVar(uriNode.GetAttribute("value")));
			}

			return brokerUri.ToString();
		}

		/// <summary>
		/// Create the NMS Factory that can create NMS Connections.  This function loads the
		/// connection settings from the configuration file.
		/// </summary>
		/// <param name="nameTestURI">The named connection configuration.</param>
		/// <returns></returns>
		protected bool CreateNMSFactory(string nameTestURI)
		{
			Uri brokerUri = null;
			string[] paths = GetConfigSearchPaths();
			object[] factoryParams = null;
			string connectionConfigFileName = GetConnectionConfigFileName();
			bool configFound = false;

			foreach(string path in paths)
			{
				string fullpath = Path.Combine(path, connectionConfigFileName);
				Tracer.Debug("\tScanning folder: " + path);

				if(File.Exists(fullpath))
				{
					Tracer.Debug("\tAssembly found!");
					connectionConfigFileName = fullpath;
					configFound = true;
					break;
				}
			}

			Assert.IsTrue(configFound, "Connection configuration file does not exist.");
			XmlDocument configDoc = new XmlDocument();

			configDoc.Load(connectionConfigFileName);
			XmlElement uriNode = (XmlElement) configDoc.SelectSingleNode(String.Format("/configuration/{0}", nameTestURI));

			if(null != uriNode)
			{
				// Replace any environment variables embedded inside the string.
				brokerUri = new Uri(ReplaceEnvVar(uriNode.GetAttribute("value")));
				factoryParams = GetFactoryParams(uriNode);
				clientId = ReplaceEnvVar(GetNodeValueAttribute(uriNode, "clientId", "NMSTestClientId"));
				userName = ReplaceEnvVar(GetNodeValueAttribute(uriNode, "userName", "guest"));
				passWord = ReplaceEnvVar(GetNodeValueAttribute(uriNode, "passWord", "guest"));
			}

			if(null == factoryParams)
			{
				NMSFactory = new Apache.NMS.NMSConnectionFactory(brokerUri);
			}
			else
			{
				NMSFactory = new Apache.NMS.NMSConnectionFactory(brokerUri, factoryParams);
			}

			return (null != NMSFactory);
		}

		private static string[] GetConfigSearchPaths()
		{
			ArrayList pathList = new ArrayList();

			// Check the current folder first.
			pathList.Add("");
#if !NETCF
			AppDomain currentDomain = AppDomain.CurrentDomain;

			// Check the folder the assembly is located in.
			pathList.Add(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
			if(null != currentDomain.BaseDirectory)
			{
				pathList.Add(currentDomain.BaseDirectory);
			}

			if(null != currentDomain.RelativeSearchPath)
			{
				pathList.Add(currentDomain.RelativeSearchPath);
			}
#endif

			return (string[]) pathList.ToArray(typeof(string));
		}

		/// <summary>
		/// Get the parameters for the ConnectionFactory from the configuration file.
		/// </summary>
		/// <param name="uriNode">Parent node of the factoryParams node.</param>
		/// <returns>Object array of parameter objects to be passsed to provider factory object.  Null if no parameters are specified in configuration file.</returns>
		protected object[] GetFactoryParams(XmlElement uriNode)
		{
			ArrayList factoryParams = new ArrayList();
			XmlElement factoryParamsNode = (XmlElement) uriNode.SelectSingleNode("factoryParams");

			if(null != factoryParamsNode)
			{
				XmlNodeList nodeList = factoryParamsNode.SelectNodes("param");

				if(null != nodeList)
				{
					foreach(XmlElement paramNode in nodeList)
					{
						string paramType = paramNode.GetAttribute("type");
						string paramValue = ReplaceEnvVar(paramNode.GetAttribute("value"));

						switch(paramType)
						{
							case "string":
								factoryParams.Add(paramValue);
								break;

							case "int":
								factoryParams.Add(int.Parse(paramValue));
								break;

							// TODO: Add more parameter types
						}
					}
				}
			}

			if(factoryParams.Count > 0)
			{
				return factoryParams.ToArray();
			}

			return null;
		}

		/// <summary>
		///
		/// </summary>
		/// <param name="parentNode"></param>
		/// <param name="nodeName"></param>
		/// <param name="dflt"></param>
		/// <returns></returns>
		protected static string GetNodeValueAttribute(XmlElement parentNode, string nodeName, string dflt)
		{
			XmlElement node = (XmlElement) parentNode.SelectSingleNode(nodeName);
			string val;

			if(null != node)
			{
				val = node.GetAttribute("value");
			}
			else
			{
				val = dflt;
			}

			return val;
		}

		/// <summary>
		/// Replace embedded variable markups with environment variable values.
		/// Variable markups are of the following form:
		///		${varname}
		/// </summary>
		/// <param name="srcText"></param>
		/// <returns></returns>
		public static string ReplaceEnvVar(string srcText)
		{
			// NOTE: Might be able to refactor to be more generic and support full variable
			// names that can be pulled from the environment.  Currently, we only support limited
			// hard-coded variable names.

			string defaultBroker = GetEnvVar("NMSTestBroker", "activemqhost");

			srcText = ReplaceEnvVar(srcText, "ActiveMQHost", defaultBroker);
			srcText = ReplaceEnvVar(srcText, "ActiveMQBackupHost", defaultBroker);

			srcText = ReplaceEnvVar(srcText, "TIBCOHost", defaultBroker);
			srcText = ReplaceEnvVar(srcText, "TIBCOBackupHost", defaultBroker);

			srcText = ReplaceEnvVar(srcText, "MSMQHost", defaultBroker);
			srcText = ReplaceEnvVar(srcText, "MSMQBackupHost", defaultBroker);
			return srcText;
		}

		/// <summary>
		/// Replace the variable with environment variable.
		/// </summary>
		/// <param name="srcText"></param>
		/// <param name="varName"></param>
		/// <param name="defaultValue"></param>
		/// <returns></returns>
		public static string ReplaceEnvVar(string srcText, string varName, string defaultValue)
		{
			string replacementValue = GetEnvVar(varName, defaultValue);
			return Regex.Replace(srcText, "\\${" + varName + "}", replacementValue, RegexOptions.IgnoreCase);
		}

		/// <summary>
		/// Get environment variable value.
		/// </summary>
		/// <param name="varName"></param>
		/// <param name="defaultValue"></param>
		/// <returns></returns>
		public static string GetEnvVar(string varName, string defaultValue)
		{
#if (PocketPC||NETCF||NETCF_2_0)
            string varValue = null;
#else
			string varValue = Environment.GetEnvironmentVariable(varName);
#endif
			if(null == varValue)
			{
				varValue = defaultValue;
			}

			return varValue;
		}

		public virtual string GetTestClientId()
		{

			System.Text.StringBuilder id = new System.Text.StringBuilder();

			id.Append("ID:");
			id.Append(this.GetType().Name);
			id.Append(":");
			id.Append(this.testRun);
			id.Append(":");
			id.Append(++idCounter);

			return id.ToString();
		}

		/// <summary>
		/// Create a new connection to the broker.
		/// </summary>
		/// <returns></returns>
		public virtual IConnection CreateConnection()
		{
			return CreateConnection(null);
		}

		/// <summary>
		/// Create a new connection to the broker.
		/// </summary>
		/// <param name="newClientId">Client ID of the new connection.</param>
		/// <returns></returns>
		public virtual IConnection CreateConnection(string newClientId)
		{
			IConnection newConnection = Factory.CreateConnection(userName, passWord);
			Assert.IsNotNull(newConnection, "connection not created");
			if(newClientId != null)
			{
				newConnection.ClientId = newClientId;
			}

			return newConnection;
		}

		public IDestination CreateDestination(ISession session, DestinationType type)
		{
			return CreateDestination(session, type, "");
		}

		public IDestination CreateDestination(ISession session, DestinationType type, string name)
		{
			switch(type)
			{
				case DestinationType.Queue:
					if(string.IsNullOrEmpty(name))
					{
						name = "queue://TEST." + this.GetType().Name + "." + Guid.NewGuid().ToString();
					}
					break;

				case DestinationType.Topic:
					if(string.IsNullOrEmpty(name))
					{
						name = "topic://TEST." + this.GetType().Name + "." + Guid.NewGuid().ToString();
					}
					break;

				case DestinationType.TemporaryQueue:
					if(string.IsNullOrEmpty(name))
					{
						name = "temp-queue://TEST." + this.GetType().Name + "." + Guid.NewGuid().ToString();
					}
					break;

				case DestinationType.TemporaryTopic:
					if(string.IsNullOrEmpty(name))
					{
						name = "temp-topic://TEST." + this.GetType().Name + "." + Guid.NewGuid().ToString();
					}
					break;

				default:
					throw new ArgumentException("type: " + type);
			}

			return CreateDestination(session, name);
		}

		/// <summary>
		/// Create a destination.  This will delete an existing destination and re-create it.
		/// </summary>
		/// <param name="session"></param>
		/// <param name="destinationName"></param>
		/// <returns></returns>
		public virtual IDestination CreateDestination(ISession session, string destinationName)
		{
			try
			{
				SessionUtil.DeleteDestination(session, destinationName);
			}
			catch(Exception)
			{
				// Can't delete it, so lets try and purge it.
				IDestination destination = SessionUtil.GetDestination(session, destinationName);

				using(IMessageConsumer consumer = session.CreateConsumer(destination))
				{
					while(consumer.Receive(TimeSpan.FromMilliseconds(750)) != null)
					{
					}
				}
			}

			return SessionUtil.GetDestination(session, destinationName);
		}

		/// <summary>
		/// Register a durable consumer
		/// </summary>
		/// <param name="connectionID">Connection ID of the consumer.</param>
		/// <param name="destination">Destination name to register.  Supports embedded prefix names.</param>
		/// <param name="consumerID">Name of the durable consumer.</param>
		/// <param name="selector">Selector parameters for consumer.</param>
		/// <param name="noLocal"></param>
		protected void RegisterDurableConsumer(string connectionID, string destination, string consumerID, string selector, bool noLocal)
		{
			using(IConnection connection = CreateConnection(connectionID))
			{
				connection.Start();
				using(ISession session = connection.CreateSession(AcknowledgementMode.DupsOkAcknowledge))
				{
					ITopic destinationTopic = (ITopic) SessionUtil.GetDestination(session, destination);
					Assert.IsNotNull(destinationTopic, "Could not get destination topic.");
					using(IMessageConsumer consumer = session.CreateDurableConsumer(destinationTopic, consumerID, selector, noLocal))
					{
						Assert.IsNotNull(consumer, "Could not create durable consumer.");
					}
				}
			}
		}

		/// <summary>
		/// Unregister a durable consumer for the given connection ID.
		/// </summary>
		/// <param name="connectionID">Connection ID of the consumer.</param>
		/// <param name="consumerID">Name of the durable consumer.</param>
		protected void UnregisterDurableConsumer(string connectionID, string consumerID)
		{
			using(IConnection connection = CreateConnection(connectionID))
			{
				connection.Start();
				using(ISession session = connection.CreateSession(AcknowledgementMode.DupsOkAcknowledge))
				{
					session.DeleteDurableConsumer(consumerID);
				}
			}
		}

		public static string ToHex(long value)
		{
			return String.Format("{0:x}", value);
		}

		public void SendMessages(IDestination destination, MsgDeliveryMode deliveryMode, int count)
		{
			IConnection connection = CreateConnection();
			connection.Start();
			SendMessages(connection, destination, deliveryMode, count);
			connection.Close();
		}

		public void SendMessages(IConnection connection, IDestination destination, MsgDeliveryMode deliveryMode, int count)
		{
			ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
			SendMessages(session, destination, deliveryMode, count);
			session.Close();
		}

		public void SendMessages(ISession session, IDestination destination, MsgDeliveryMode deliveryMode, int count)
		{
			IMessageProducer producer = session.CreateProducer(destination);
			producer.DeliveryMode = deliveryMode;
			for(int i = 0; i < count; i++)
			{
				producer.Send(session.CreateTextMessage("" + i));
			}
			producer.Close();
		}

		protected void AssertTextMessagesEqual(IMessage[] firstSet, IMessage[] secondSet)
		{
			AssertTextMessagesEqual(firstSet, secondSet, "");
		}

		protected void AssertTextMessagesEqual(IMessage[] firstSet, IMessage[] secondSet, string messsage)
		{
			Assert.AreEqual(firstSet.Length, secondSet.Length, "Message count does not match: " + messsage);

			for(int i = 0; i < secondSet.Length; i++)
			{
				ITextMessage m1 = firstSet[i] as ITextMessage;
				ITextMessage m2 = secondSet[i] as ITextMessage;

				AssertTextMessageEqual(m1, m2, "Message " + (i + 1) + " did not match : ");
			}
		}

		protected void AssertEquals(ITextMessage m1, ITextMessage m2)
		{
			AssertEquals(m1, m2, "");
		}

		protected void AssertTextMessageEqual(ITextMessage m1, ITextMessage m2, string message)
		{
			Assert.IsFalse(m1 == null ^ m2 == null, message + ": expected {" + m1 + "}, but was {" + m2 + "}");

			if(m1 == null)
			{
				return;
			}

			Assert.AreEqual(m1.Text, m2.Text, message);
		}

		protected void AssertEquals(IMessage m1, IMessage m2)
		{
			AssertEquals(m1, m2, "");
		}

		protected void AssertEquals(IMessage m1, IMessage m2, string message)
		{
			Assert.IsFalse(m1 == null ^ m2 == null, message + ": expected {" + m1 + "}, but was {" + m2 + "}");

			if(m1 == null)
			{
				return;
			}

			Assert.IsTrue(m1.GetType() == m2.GetType(), message + ": expected {" + m1 + "}, but was {" + m2 + "}");

			if(m1 is ITextMessage)
			{
				AssertTextMessageEqual((ITextMessage) m1, (ITextMessage) m2, message);
			}
			else
			{
				Assert.AreEqual(m1, m2, message);
			}
		}
	}
}
